// Functions associated with the UserInput class
//-----------------

//-----------------
// Includes libraries and other files
#include "UserInput.h"

//-----------------

//-----------------
//// CONSTRUCTOR - UserInput
UserInput::UserInput()
	: g_pDI( NULL), g_pDIMouseDevice( NULL), g_pDIKeyboardDevice( NULL), delay_( 0)
{
	ZeroMemory( press_current, 256);
	ZeroMemory( press_prev, 256);
	ZeroMemory( &mouse_state_current, sizeof( mouse_state_current));
	ZeroMemory( &mouse_state_prev, sizeof( mouse_state_prev));
}

//// DESTRUCTOR - UserInput
UserInput::~UserInput() {
	clean_up_direct_input();
}
//-----------------

//-----------------
//// FUNCTION - get_mouse_movement
// get the x and y position of the mouse
void UserInput::get_mouse_movement( float& x, float& y) const {
	x = mouse_state_current.lX;
	y = mouse_state_current.lY;
}

//// FUNCTION - get_mouse_position
// get the amount of movement in the x and y of the mouse
void UserInput::get_mouse_position( float& x, float& y, const int& w, const int& h) const {
	// if fixed values are used as the modifiers then the 'else if' that deals with the X
	// needs to be '-=' and the 'if' that deals with the Y needs to be '+='
	float tolerance( 0), modifierX( mouse_state_current.lX), modifierY( mouse_state_current.lY);

	if( mouse_state_current.lX > tolerance)
		x += modifierX;
	else if( mouse_state_current.lX < -tolerance)
		x += modifierX;

	if( mouse_state_current.lY > tolerance)
		y -= modifierY;
	else if( mouse_state_current.lY < -tolerance)
		y -= modifierY;

	// ensure the cursor never goes beyond the boundries of the window
	if( x > w)
		x = w;
	else if( x < 0)
		x = 0;

	if( y > h)
		y = h;
	else if( y < 0)
		y = 0;
}

//// FUNCTION - get_mouse_wheel_movement
// get the amount of movement of the mouse wheel
float UserInput::get_mouse_wheel_movement() const {
	// Pre-conditions :- none
	assert( true);

	return mouse_state_current.lZ;
}

//// FUNCTION - button_is_pressed
// check if mouse button is pressed
bool UserInput::button_is_pressed( const int button) const {
	if( mouse_state_current.rgbButtons[button] & 0x80 && !( mouse_state_prev.rgbButtons[button] & 0x80))
		return true;
	else
		return false;
}

//// FUNCTION - button_is_held
// check if mouse button is held
bool UserInput::button_is_held( const int button) const {
	if( mouse_state_current.rgbButtons[button] & 0x80)
		return true;
	else
		return false;
}

//// FUNCTION - key_is_pressed
// check if the key has just been pressed
bool UserInput::key_is_pressed( const int key) const {
	if( KEYDOWN( press_current, key) && !KEYDOWN( press_prev, key))
		return true;
	else
		return false;
}

//// FUNCTION - key_is_held
// check if the key is being held down
bool UserInput::key_is_held( const int key) const {
	if( KEYDOWN( press_current, key))
		return true;
	else
		return false;
}

//// FUNCTION - setup_direct_input
// create and initialise the DirectInput object
BOOL WINAPI UserInput::setup_direct_input( HINSTANCE g_hinst, HWND g_hwndMain) {
	HRESULT hr;

	// create the DirectInput object
	hr = DirectInput8Create( g_hinst, DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&g_pDI, NULL);
	if FAILED( hr)
		return FALSE;

	// initialise the mouse:
	// retrieve a pointer to an IDirectInputDevice8 interface for the mouse
	hr = g_pDI->CreateDevice( GUID_SysMouse, &g_pDIMouseDevice, NULL);
	if FAILED( hr) {
		clean_up_direct_input();
		return FALSE;
	}

	// set the data format using the predefined mouse data format
	hr = g_pDIMouseDevice->SetDataFormat( &c_dfDIMouse2);
	if FAILED( hr) {
		clean_up_direct_input();
		return FALSE;
	}

	// set the cooperative level
	hr = g_pDIMouseDevice->SetCooperativeLevel( g_hwndMain, DISCL_FOREGROUND | DISCL_EXCLUSIVE);
	if FAILED( hr) {
		clean_up_direct_input();
		return FALSE;
	}

	g_pDIMouseDevice->Acquire();

	// initialise the keyboard:
	// retrieve a pointer to an IDirectInputDevice8 interface
	hr = g_pDI->CreateDevice( GUID_SysKeyboard, &g_pDIKeyboardDevice, NULL);
	if FAILED( hr) {
		clean_up_direct_input();
		return FALSE;
	}

	// set the data format using the predefined mouse data format
	hr = g_pDIKeyboardDevice->SetDataFormat( &c_dfDIKeyboard);
	if FAILED( hr) {
		clean_up_direct_input();
		return FALSE;
	}

	return TRUE;
}

//// FUNCTION - process_input
// get the current key press
void UserInput::process_input() {
	process_keyboard_input();
	process_mouse_input();
}

//// FUNCTION - get_key_pressed
// return the button that was pressed
char UserInput::get_key_pressed() {
	delay_++;
	char key = NULL;
	if( key_is_pressed( DIK_A))
		key = 'a';
	else if( key_is_pressed( DIK_B))
		key = 'b';
	else if( key_is_pressed( DIK_C))
		key = 'c';
	else if( key_is_pressed( DIK_D))
		key = 'd';
	else if( key_is_pressed( DIK_E))
		key = 'e';
	else if( key_is_pressed( DIK_F))
		key = 'f';
	else if( key_is_pressed( DIK_G))
		key = 'g';
	else if( key_is_pressed( DIK_H))
		key = 'h';
	else if( key_is_pressed( DIK_I))
		key = 'i';
	else if( key_is_pressed( DIK_J))
		key = 'j';
	else if( key_is_pressed( DIK_K))
		key = 'k';
	else if( key_is_pressed( DIK_L))
		key = 'l';
	else if( key_is_pressed( DIK_M))
		key = 'm';
	else if( key_is_pressed( DIK_N))
		key = 'n';
	else if( key_is_pressed( DIK_O))
		key = 'o';
	else if( key_is_pressed( DIK_P))
		key = 'p';
	else if( key_is_pressed( DIK_Q))
		key = 'q';
	else if( key_is_pressed( DIK_R))
		key = 'r';
	else if( key_is_pressed( DIK_S))
		key = 's';
	else if( key_is_pressed( DIK_T))
		key = 't';
	else if( key_is_pressed( DIK_U))
		key = 'u';
	else if( key_is_pressed( DIK_V))
		key = 'v';
	else if( key_is_pressed( DIK_W))
		key = 'w';
	else if( key_is_pressed( DIK_X))
		key = 'x';
	else if( key_is_pressed( DIK_Y))
		key = 'y';
	else if( key_is_pressed( DIK_Z))
		key = 'z';
	else if( key_is_pressed( DIK_1))
		key = '1';
	else if( key_is_pressed( DIK_2))
		key = '2';
	else if( key_is_pressed( DIK_3))
		key = '3';
	else if( key_is_pressed( DIK_4))
		key = '4';
	else if( key_is_pressed( DIK_5))
		key = '5';
	else if( key_is_pressed( DIK_6))
		key = '6';
	else if( key_is_pressed( DIK_7))
		key = '7';
	else if( key_is_pressed( DIK_8))
		key = '8';
	else if( key_is_pressed( DIK_9))
		key = '9';
	else if( key_is_pressed( DIK_0))
		key = '0';
	else if( key_is_pressed( DIK_SPACE))
		key = ' ';
	else if( key_is_held( DIK_BACK) && delay_ > 3) {
		key = '~';
		delay_ = 0;
	}

	return key;
}

//// FUNCTION - clean_up_direct_input
// release the objects so they can be deleted
void WINAPI UserInput::clean_up_direct_input() {
	if( g_pDI) {
		// release the mouse
		if( g_pDIMouseDevice) {
			// always unacquire device before calling Release();
			g_pDIMouseDevice->Unacquire();
			g_pDIMouseDevice->Release();
			g_pDIMouseDevice = NULL;
		}

		// release the keyboard
		if( g_pDIKeyboardDevice) {
			// always unacquire device before calling Release();
			g_pDIKeyboardDevice->Unacquire();
			g_pDIKeyboardDevice->Release();
			g_pDIKeyboardDevice = NULL;
		}

		// release the DirectInput object
		g_pDI->Release();
		g_pDI = NULL;
	}
}

//// FUNCTION - process_mouse_input
// record any recent mouse activity
void WINAPI UserInput::process_mouse_input() {
	HRESULT hr;

	if( g_pDIMouseDevice != NULL) {
		mouse_state_prev = mouse_state_current;

		// get the input device's state and store it in 'mouse_state'
		ZeroMemory( &mouse_state_current, sizeof( mouse_state_current));
		hr = g_pDIMouseDevice->GetDeviceState( sizeof( DIMOUSESTATE2), &mouse_state_current);
		if( FAILED( hr)) {
			// DirectInput may be telling us that the input stream has been interrupted. We aren't
			// tracking any state between polls, so we don't have any special reset that needs to
			// be done. We just re-acquire and try again.

			// if input is lost, then try to acquire and keep trying until acquired
			do {
				hr = g_pDIMouseDevice->Acquire();
			} while( hr == DIERR_INPUTLOST);

			hr = g_pDIMouseDevice->GetDeviceState( sizeof( DIMOUSESTATE2), &mouse_state_current);
		}
	}
}

//// FUNCTION - process_keyboard_input
// record any recent keyboard activity
void WINAPI UserInput::process_keyboard_input() {
	HRESULT hr;

	for( int i( 0); i < 256; i++)
		press_prev[i] = press_current[i];
	// clear the keyboard buffed, just in case
	ZeroMemory( press_current, 256);

	hr = g_pDIKeyboardDevice->GetDeviceState( sizeof( press_current), ( LPVOID)&press_current);
	if FAILED( hr) {
		// if this failed then the device has probably been lost; so re-acquire it
		do {
			hr = g_pDIKeyboardDevice->Acquire();
		} while( hr == DIERR_INPUTLOST);

		hr = g_pDIKeyboardDevice->GetDeviceState( sizeof( press_current), (LPVOID)&press_current);
	}
}